﻿namespace Ngs.Reflection
{
    using System;
    using System.Linq.Expressions;
    using System.Reflection;

    public static class Reflector
    {
        static Reflector()
        {
        }

        public static Func<T, object> GetValue<T>(this PropertyInfo property)
        {
            ParameterExpression targetObject = Expression.Parameter(typeof(T), "target");

            var lambda = Expression.Lambda(Expression.Convert(Expression.Property(targetObject, property), typeof(object)), targetObject);

            var compiled = (Func<T, object>)lambda.Compile();
            return compiled;

            // [Old implementation did not pass Unit test (Probably due to Value vs Reference types)
            //MethodInfo getMethod = property.GetGetMethod();
            //Func<T, object> getter = (Func<T, object>)Delegate.CreateDelegate(typeof(Func<T, object>), getMethod);
            //return getter; // Left for clarity.
        }

        public static Action<T, object> SetValue<T>(this PropertyInfo property)
        {
            ExceptionFactory.ThrowIfArgumentNull(property);

            MethodInfo setMethod = property.GetSetMethod();
            ParameterInfo p = setMethod.GetParameters()[0];

            Type valueType = p.ParameterType;

            ParameterExpression targetObject = Expression.Parameter(typeof(T), "target");
            ParameterExpression valueExp = Expression.Parameter(typeof(object), "value");

            // Need convert before call otherwise implicit cast throws exception
            // Ex: void set_Name((string)obj); // where obj is of type System.Object.
            var assigment = Expression.Call(targetObject, setMethod, Expression.Convert(valueExp, valueType));

            var lambda = Expression.Lambda(assigment, targetObject, valueExp);

            var compiled = (Action<T, object>)lambda.Compile();
            return compiled;
        }
    }
}
